package com.liruo.treasureattic.config.common.jackson;

import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.jackson.JacksonProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.KotlinDetector;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.time.*;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * timestamp: write-dates-as-timestamps: false
 * 		write-date-timestamps-as-nanoseconds: false表示使用millis，否则默认使用ns
 * 	如Local开头的时间	会序列化为[2023,3,1,21,15,51,254]---最好使用Instant接收json，其他有spring的LongToInstantConverter
 * string:   date-format: yyyy-MM-dd hh:mm:ss 或者默认
 *
 *   jackson:
 *     default-property-inclusion: non_null
 *     serialization:
 *       #bean没有属性不用抛出异常
 *       fail-on-empty-beans: false
 *       #FAIL_ON_UNKNOWN_PROPERTIES=false是默认的--忽视未知json属性
 *       write-dates-as-timestamps: false
 *       write-date-timestamps-as-nanoseconds: false
 *     date-format: yyyy-MM-dd hh:mm:ss
 */
@ConditionalOnClass(ObjectMapper.class)
@AutoConfiguration(before = JacksonAutoConfiguration.class)
@EnableConfigurationProperties(JacksonProperties.class)
@Slf4j
public class JacksonConfiguration {
	@Resource
	private JacksonProperties jacksonProperties;

	@Bean
	public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilder() {
		String allowTimeName = Stream.of(LocalDateTime.class,
						Date.class,
						LocalDate.class,
						LocalTime.class,
						Instant.class)
				.map(Class::getName)
				.collect(Collectors.joining(", "));

		//timestamp
		if(!CollectionUtils.isEmpty(this.jacksonProperties.getSerialization())){
			Boolean useTimestamp = this.jacksonProperties.getSerialization().get(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
			if(Boolean.TRUE.equals(useTimestamp)){
				if(log.isDebugEnabled()){
					log.debug("{} set time class use timestamp to deserialize and serialize == {}",
							JacksonConfiguration.class.getName(),
							allowTimeName
					);
				}
				return builder->{};
			}
		}

		//string
		String pattern = this.jacksonProperties.getDateFormat();
		if(log.isDebugEnabled()){
			log.debug("{} set time class use pattern[{}] to deserialize and serialize == {}",
					JacksonConfiguration.class.getName(),
					StringUtils.hasText(pattern) ? pattern : JavaTimeModule.DEFAULT_PATTERN,
					allowTimeName
			);
		}

		List<Module> modulesToRegister = new ArrayList<>();
		TimeZone timeZone = this.jacksonProperties.getTimeZone();
		Locale locale = this.jacksonProperties.getLocale();
		if(timeZone == null){
			timeZone = TimeZone.getDefault();
		}
		ZoneId zoneId = ZoneId.of(timeZone.getID());
		if(locale == null){
			locale = Locale.getDefault(Locale.Category.FORMAT);
		}
		// jackson-datatype-jsr310  available
		Module javaTimeModule = StringUtils.hasText(pattern) ?
				new JavaTimeModule(pattern, locale, zoneId) :
				new JavaTimeModule(locale, zoneId);
		modulesToRegister.add(javaTimeModule);

		registerWellKnownModulesIfAvailable(modulesToRegister);


		return builder -> builder.modules(modulesToRegister);
	}

	/**
	 * Jackson2ObjectMapperBuilder#registerWellKnownModulesIfAvailable(MultiValueMap)
	 */
	@SuppressWarnings("unchecked")
	private void registerWellKnownModulesIfAvailable(List<Module> modulesToRegister) {
		ClassLoader moduleClassLoader = getClass().getClassLoader();
		try {
			Class<? extends Module> jdk8ModuleClass = (Class<? extends Module>)
					ClassUtils.forName("com.fasterxml.jackson.datatype.jdk8.Jdk8Module", moduleClassLoader);
			Module jdk8Module = BeanUtils.instantiateClass(jdk8ModuleClass);
			modulesToRegister.add(jdk8Module);
		}
		catch (ClassNotFoundException ex) {
			// jackson-datatype-jdk8 not available
		}


		// Joda-Time 2.x present?
		if (ClassUtils.isPresent("org.joda.time.YearMonth", moduleClassLoader)) {
			try {
				Class<? extends Module> jodaModuleClass = (Class<? extends Module>)
						ClassUtils.forName("com.fasterxml.jackson.datatype.joda.JodaModule", moduleClassLoader);
				Module jodaModule = BeanUtils.instantiateClass(jodaModuleClass);
				modulesToRegister.add(jodaModule);
			}
			catch (ClassNotFoundException ex) {
				// jackson-datatype-joda not available
			}
		}

		// Kotlin present?
		if (KotlinDetector.isKotlinPresent()) {
			try {
				Class<? extends Module> kotlinModuleClass = (Class<? extends Module>)
						ClassUtils.forName("com.fasterxml.jackson.module.kotlin.KotlinModule", moduleClassLoader);
				Module kotlinModule = BeanUtils.instantiateClass(kotlinModuleClass);
				modulesToRegister.add(kotlinModule);
			}
			catch (ClassNotFoundException ex) {
				// jackson-module-kotlin not available
			}
		}
	}
}

